﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using System.Xml;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;
using FarseerGames.FarseerPhysics.Collisions;
using FolderData;

namespace Folder
{
    public static class RenderingHelper
    {
        static GraphicsDevice GraphicsDevice;
        static SpriteBatch spriteBatch;

        static ScreenManager ScreenManager;
        static Effect effect, spriteEffect;
        static Vector2 viewportSize;
        static Texture2D boundTexture;
        static ResolveTexture2D foldedBgTexture, foldedTexture, readyTexture;

        /// <summary>
        /// RenderingHelper를 초기화한다. 이 renderingHelper는 folding/unfolding시 shader를 이용하여
        /// map을 그리는 역할을 위임받아 수행하게 된다.
        /// </summary>
        /// <param name="screenManger">현재 screenManager. ScreenManager로부터 graphicsDevice와 spriteBatch를 받아 사용한다.</param>
        public static void Initialize(ScreenManager screenManger)
        {
            ScreenManager = screenManger;
            GraphicsDevice = screenManger.GraphicsDevice;
            spriteBatch = screenManger.SpriteBatch;
            effect = screenManger.Game.Content.Load<Effect>("shader/Shader2");
            spriteEffect = screenManger.Game.Content.Load<Effect>("shader/SpriteBatch");
            boundTexture = screenManger.Game.Content.Load<Texture2D>("boundary");

            foldedTexture = new ResolveTexture2D(GraphicsDevice, Camera.SCREEN_WIDTH, Camera.SCREEN_HEIGHT, 1, SurfaceFormat.Color);
            foldedBgTexture = new ResolveTexture2D(GraphicsDevice, Camera.SCREEN_WIDTH, Camera.SCREEN_HEIGHT, 1, SurfaceFormat.Color);
            readyTexture = new ResolveTexture2D(GraphicsDevice, Camera.SCREEN_WIDTH, Camera.SCREEN_HEIGHT, 1, SurfaceFormat.Color);

            Viewport viewport = GraphicsDevice.Viewport;
            viewportSize = new Vector2(viewport.Width, viewport.Height);
        }

        /// <summary>
        /// GraphicDevice가 lost되었을 때 복구되지 않는 resolveTexture2D를 다시 생성한다.
        /// </summary>
        /// <param name="graphicsDevice">새로 생성된 GraphicsDevice</param>
        public static void ResetDevice(GraphicsDevice graphicsDevice)
        {
            foldedTexture = new ResolveTexture2D(GraphicsDevice, Camera.SCREEN_WIDTH, Camera.SCREEN_HEIGHT, 1, SurfaceFormat.Color);
            foldedBgTexture = new ResolveTexture2D(GraphicsDevice, Camera.SCREEN_WIDTH, Camera.SCREEN_HEIGHT, 1, SurfaceFormat.Color);
            readyTexture = new ResolveTexture2D(GraphicsDevice, Camera.SCREEN_WIDTH, Camera.SCREEN_HEIGHT, 1, SurfaceFormat.Color);
        }

        /// <summary>
        /// 게임의 장면을 장면을 랜더링한다.
        /// </summary>
        /// <param name="map">랜더링 할 맵</param>
        /// <param name="state">현재 화면의 FoldState</param>
        /// <param name="foldingVec">화면 접기의 방향</param>
        /// <param name="foldingPos">화면 접기의 위치</param>
        /// <param name="foldingDegree">화면 접기의 각도</param>
        /// <param name="targetTexture">렌더링 결과를 저장할 texture. null일 경우 렌더링 결과를 화면에 출력한다.</param>
        /// <param name="color">화면에 그릴 색</param>
        public static void DrawScene(Map map, FoldState state,
            Vector3 foldingVec, Vector3 foldingPos, float foldingDegree, 
            ResolveTexture2D targetTexture, Color color)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteEffect.Parameters["ViewportSize"].SetValue(viewportSize);

            if (state == FoldState.FOLDREADY || state == FoldState.FOLDANCHORED)
                spriteEffect.Parameters["Satuation"].SetValue(1.0f);
            else
                spriteEffect.Parameters["Satuation"].SetValue(0.0f);

            spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState);
            spriteEffect.Begin();
            spriteEffect.CurrentTechnique.Passes[0].Begin();


            if(map.Background != null)
                map.Background.Draw(spriteBatch, Vector2.Zero - map.Camera.Position + map.BgPosition, color);

            map.DrawFoldables(spriteBatch, color);

            if (state == FoldState.FOLDREADY || state == FoldState.FOLDANCHORED)
            {
                spriteEffect.Parameters["Satuation"].SetValue(0.0f);
                map.DrawUnfoldables(spriteBatch, color);
            }

            spriteBatch.End();
            spriteEffect.CurrentTechnique.Passes[0].End();
            spriteEffect.End();


            if (state == FoldState.FOLDED || state == FoldState.FOLDING || state == FoldState.UNFOLDING)
            {
                Matrix View = Matrix.CreateLookAt(new Vector3(map.Camera.Position, -870),
                    new Vector3(map.Camera.Position, 0),
                    Vector3.Down);
                Matrix Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
                    (float)Camera.SCREEN_WIDTH / Camera.SCREEN_HEIGHT,
                    1,
                    5000);

                Vector2 pointerPos = new Vector2(foldingPos.X, foldingPos.Y);


                foldingVec.Normalize();

                Vertices box = new Vertices();
                float theta = (float)Math.Atan2(foldingVec.Y, foldingVec.X);
                {
                    box.Add(new Vector2(-100, -100));
                    box.Add(new Vector2(-100, 100));
                    box.Add(new Vector2(100, 100));
                    box.Add(new Vector2(100, -100));
                    for (int i = 0; i < 4; i++)
                    {
                        box[i] = new Vector2((float)Math.Sin(theta) * box[i].X + (float)Math.Cos(theta) * box[i].Y,
                                            (float)-Math.Cos(theta) * box[i].X + (float)Math.Sin(theta) * box[i].Y);
                        box[i] += pointerPos;
                    }
                }
                VertexPositionNormalTexture[] vpnt = new VertexPositionNormalTexture[4];
                for (int i = 0; i < 4; i++)
                {
                    vpnt[i].Position = new Vector3(box[i], 0.0f);
                    vpnt[i].TextureCoordinate = new Vector2(box[i].X / Camera.SCREEN_WIDTH, box[i].Y / Camera.SCREEN_HEIGHT);
                }
                Vector3 offset = new Vector3(100 * (float)Math.Cos(theta), 100 * (float)Math.Sin(theta), 0);

                effect.Begin();
                {

                    //접혀서 배경에 그려지는 부분을 표현한다.
                    {
                        Matrix World = Matrix.CreateTranslation(new Vector3(map.foldingCameraPosition - Camera.ScreenCenter, 0));

                        effect.Parameters["matWorldViewProj"].SetValue(World * View * Projection);
                        effect.Parameters["Texture"].SetValue(foldedBgTexture);
                        effect.Parameters["color"].SetValue(Color.White.ToVector4());

                        foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                        {
                            pass.Begin();

                            GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>(
                                PrimitiveType.TriangleList, vpnt, 0, 4, new int[] { 3, 0, 2, 0, 1, 2, 2, 0, 3, 2, 1, 0 }, 0, 4);

                            pass.End();
                        }
                    }

                    //접는 에니메이션과 접힌 부분을 처리한다.
                    {
                        Matrix World = Matrix.CreateTranslation(-foldingPos - offset) *
                            Matrix.CreateFromAxisAngle(new Vector3(foldingVec.Y, -foldingVec.X, 0), foldingDegree) *
                            Matrix.CreateTranslation(foldingPos + offset) *
                            Matrix.CreateTranslation(new Vector3(map.foldingCameraPosition - Camera.ScreenCenter, 0));

                        effect.Parameters["matWorldViewProj"].SetValue(World * View * Projection);
                        effect.Parameters["Texture"].SetValue(foldedTexture);
                        effect.Parameters["color"].SetValue(Color.White.ToVector4());

                        foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                        {
                            pass.Begin();

                            GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>(
                                PrimitiveType.TriangleList, vpnt, 0, 4, new int[] { 3, 0, 2, 0, 1, 2, 2, 0, 3, 2, 1, 0 }, 0, 4);

                            pass.End();
                        }
                    }
                }
                effect.End();
            }


            spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState);
            spriteEffect.Begin();
            spriteEffect.CurrentTechnique.Passes[0].Begin();

            map.DrawCharacters(spriteBatch, color);

            spriteBatch.End();
            spriteEffect.CurrentTechnique.Passes[0].End();
            spriteEffect.End();

            if (targetTexture != null)
            {
                GraphicsDevice.ResolveBackBuffer(targetTexture);

                GraphicsDevice.Clear(Color.CornflowerBlue);
            }
        }

        public static void RenderFoldingTextures(Map map, FoldState state, Vector2 pointerPos,
            Vector2 arrowDir, Color color)
        {
            DrawScenery(map, state, pointerPos, arrowDir, foldedBgTexture, color);
            DrawFoldedScene(map, pointerPos, arrowDir, foldedTexture, color);
        }

        private static void DrawScenery(Map map, FoldState state, Vector2 pointerPos,
            Vector2 arrowDir, ResolveTexture2D texture, Color color)
        {
            GraphicsDevice.Clear(new Color(0, 0, 0, 0));

            spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState);

            if (map.Background != null)
                map.Background.Draw(spriteBatch, map.BgPosition - map.Camera.Position, color);

            if (state == FoldState.FOLDING || state == FoldState.UNFOLDING || state == FoldState.FOLDED)
            {
                spriteBatch.Draw(boundTexture, pointerPos, null, Color.Gray,
                    (float)Math.Atan2(-arrowDir.Y, arrowDir.X),
                    new Vector2(100, 100), 1.0f, SpriteEffects.None, 0);
            }

            spriteBatch.End();

            GraphicsDevice.ResolveBackBuffer(texture);

            GraphicsDevice.Clear(new Color(0, 0, 0, 0));
        }

        private static void DrawFoldedScene(Map map, Vector2 pointerPos,
            Vector2 arrowDir, ResolveTexture2D targetTexture, Color color)
        {
            GraphicsDevice.Clear(new Color(0, 0, 0, 0));

            spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState);

            map.DrawFoldables(spriteBatch, color);

            spriteBatch.Draw(boundTexture, pointerPos, null, Color.Black,
                (float)Math.Atan2(-arrowDir.Y, arrowDir.X),
                new Vector2(100, 100), 1.0f, SpriteEffects.None, 0);

            spriteBatch.End();

            GraphicsDevice.ResolveBackBuffer(targetTexture);

            GraphicsDevice.Clear(new Color(0, 0, 0, 0));
        }
    }
}
